/*
 * Jenny Ruppen 09-730-391
 * Sebastien Goldberg 08-914-855
 * Henry Raymond 08-928-061
 * Stefan Mussato 09-752-270
 */
 
package ch.uzh.ifi.sopra.gruppe9.game;

import java.util.ArrayList;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetManager;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.Vector2f;
import com.jme3.scene.Node;


/*
 * This class controls all 2D graphical user interface elements.
 */
public class GUI {
	
	
	/**
	 * Enumerator of possible screen offsets.
	 */
	public enum Offset {NONE, WINDOW, SCENE, PANEL};
	
	
	/**
	 * Simple application object of jME3.
	 */
	private static SimpleApplication game;

	
	/**
	 * Main 2D GUI node from jME3.
	 */
	private static Node guiNode;
	
	
	/**
	 * Main 3D GUI node from jME3.
	 */
	private static Node rootNode;
	
	
	/**
	 * Asset Manager of jME3
	 */
	private static AssetManager assetManager;
	
	
	/**
	 * Input Manager of jME3
	 */
	private static InputManager inputManager;
	
	
	/**
	 * Width of jME3 window.
	 */
	private static int windowWidth;
	
	
	/**
	 * Height of jME3 window.
	 */
	private static int windowHeight;
	
	
	/**
	 * Width of right game panel.
	 */
	private static int panelWidth;
	
	
	/**
	 * List of all registered rollOver event elements.
	 */
	private static ArrayList<IMouseOverable> mouseOverables = new ArrayList<IMouseOverable>();
	
	
	/**
	 * List of all rollOver event elements on which the cursor currently hovers.
	 */
	private static ArrayList<IMouseOverable> currentMouseOverables = new ArrayList<IMouseOverable>();
	
	
	/**
	 * List of all registered click event elements.
	 */
	private static ArrayList<IClickable> clickables = new ArrayList<IClickable>();
	
	
	/**
	 * List of all registered elements to be updated at each update cycle.
	 */
	private static ArrayList<IUpdatable> updatables = new ArrayList<IUpdatable>();
	
	
	/**
	 * List of all registered elements that are unselectable.
	 */
	private static ArrayList<IUnselectable> unselectables = new ArrayList<IUnselectable>();
	
	
	/**
	 * ActionListener for mouse clicks.
	 */
	private static ActionListener actionListener;
	
	
	/**
	 * Current state of the GUI. Defining which elements are shown in the window.
	 */
	private static GuiState state;
	
	
	/**
	 * True if in-game screen with map and panel is displayed
	 */
	private static boolean inGameOn = false;
	
	
	/**
	 * Screen displaying text input field asking player to enter host for connection.
	 */
	private static ConnectionScreen connectionScreen;
	
	
	/**
	 * Screen displayed before the beginning of the game with list of all connected 
	 * players where player name and color can be chosen.
	 */
	private static GameSetupScreen gameSetupScreen;
	
	
	/**
	 * Right-hand panel of the in-game screen.
	 */
	private static Panel panel;
	
	
	/**
	 * In-game bank window.
	 */
	private static BankInterface bank;
	
	
	/**
	 * In-game palace of the caliph window.
	 */
	private static PalaceOfTheCaliphInterface palaceOfTheCaliph;
	
	
	/**
	 * In-game tower element market window.
	 */
	private static TowerElementMarketInterface towerElementMarket;
	
	
	/**
	 * In-game house of spies window.
	 */
	private static HouseOfSpiesInterface houseOfSpies;
	
	
	/**
	 * In-game construction authority window.
	 */
	private static ConstructionAuthorityInterface constructionAuthority;
	
	
	/**
	 * In-game window with cheats.
	 */
	private static CheatScreen cheatScreen;
	
	
	/**
	 * Sets up GUI.
	 */
	public static void setup(SimpleApplication game, Node newGuiNode, Node newRootNode, InputManager newInputManager, AssetManager newAssetManager, int newWindowWidth, int newWindowHeight) {
		
		GUI.game = game;
		
		guiNode = newGuiNode;
        guiNode.detachAllChildren();
        
        rootNode = newRootNode;
        
        inputManager = newInputManager;
        
        assetManager = newAssetManager;
        
        windowWidth = newWindowWidth;
        windowHeight = newWindowHeight;
        
        panelWidth = 2 * (int) Math.round((float)windowHeight / 6.0f);
        
		inputManager.addMapping("Click", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
		inputManager.addMapping("FreeCam", new KeyTrigger(KeyInput.KEY_F7));
		inputManager.addMapping("CheatScreen", new KeyTrigger(KeyInput.KEY_F8));
		actionListener = new ActionListener() {
			public void onAction(String name, boolean keyPressed, float tpf) {
		    	if(name.equals("Click")) {
		    		GUI.processMouseClick(keyPressed);
		    	} else if(name.equals("FreeCam")) {
		    		if(!keyPressed) {
		    			Scenery.toggleFreeCam();
		    		}
		    	} else if(name.equals("CheatScreen")) {
		    		if(!keyPressed && state.getState() != GuiState.State.CHEATS) {
		    			GUI.changeState(GuiState.State.CHEATS);
		    		}
		    	}
			}
		};
		inputManager.addListener(actionListener, new String[] {"Click", "FreeCam", "CheatScreen"});
		
		connectionScreen = new ConnectionScreen();
		guiNode.attachChild(connectionScreen);
		
		gameSetupScreen = new GameSetupScreen();
		guiNode.attachChild(gameSetupScreen);
		
		panel = new Panel();
		guiNode.attachChild(panel);
		
		Scenery.setup(assetManager);
		
		bank = new BankInterface();
		guiNode.attachChild(bank);
		
		towerElementMarket = new TowerElementMarketInterface();
		guiNode.attachChild(towerElementMarket);
		
		palaceOfTheCaliph = new PalaceOfTheCaliphInterface();
		guiNode.attachChild(palaceOfTheCaliph);
		
		houseOfSpies = new HouseOfSpiesInterface();
		guiNode.attachChild(houseOfSpies);
		
		constructionAuthority = new ConstructionAuthorityInterface();
		guiNode.attachChild(constructionAuthority);
		
		cheatScreen = new CheatScreen();
		guiNode.attachChild(cheatScreen);
		
	}
	
	
	/**
	 * Start GUI within application.
	 */
	public static void start() {
		state = new GuiState(GuiState.State.CONNECTION_SCREEN);
		state.start();
	}
	
	
	/**
	 * Change the current GUI state. (End present state and starts new one.)
	 */
	public static void changeState(GuiState.State newState) {
		state.end();
		state = new GuiState(newState);
		state.start();
	}
	
	
	/**
	 * Get main jME3 GUI node.
	 */
	public static Node getNode() {
		return guiNode;
	}
	
	
	/**
	 * Get scale of pixel value depending on window resolution. (1.0 scale is at 1024*768) 
	 */
	public static float getPixelScale() {
		return ((float)windowHeight) / 768.0f;
	}
	
	
	/**
	 * Get offset of X coordinates for offset type.
	 */
	public static float getOffsetX(Offset offset) {
		float offsetX = 0.0f;
		if(offset == Offset.PANEL) {
			offsetX = (windowWidth - panelWidth);
		} else if(offset == Offset.SCENE) {
			offsetX = ((float)(windowWidth - panelWidth - windowHeight) / 2.0f);
		} else if(offset == Offset.WINDOW || offset == Offset.NONE) {
			offsetX = 0.0f;
		}
		return offsetX;
	}
	
	
	/**
	 * Calculate actual X coordinate from virtual coordinate starting at given offset
	 * assuming 1024*768 window resolution and 0/0 in top left corner.
	 */
	public static float calculateX(Offset offset, int x) {
		return getOffsetX(offset) + ((float)x) * getPixelScale();
	}
	
	
	/**
	 * Calculate actual Y coordinate from virtual coordinate starting at given offset 
	 * assuming 1024*768 window resolution and 0/0 in top left corner.
	 */
	public static float calculateY(Offset offset, int y) {
		return ((float)(768 - y)) * getPixelScale();
	}
	
	
	/**
	 * Get width/height ratio of window dimensions. 
	 */
	public static float getWindowRatio() {
		return ((float)windowWidth) / ((float)windowHeight);
	}
	
	
	/**
	 * Get window width scaled to an assumed window height of 768 pixel;
	 */
	public static int getScaledWindowWidth() {
		return (int)(((float)windowWidth) / getPixelScale());
	}
	
	
	/**
	 * Get rescaled font size so that the size is 24px at 1024*768 window dimension.
	 */
	public static float getFontSize() {
		return 20.0f * getPixelScale();
	}


	/**
	 * Registers list of elements to the MouseOver event element list.
	 */
	public static void registerMouseOverable(ArrayList<IMouseOverable> newMouseOverables) {
		mouseOverables.addAll(newMouseOverables);
	}
	
	
	/**
	 * Unregisters list of elements to the MouseOver event element list.
	 */
	public static void unregisterMouseOverable(ArrayList<IMouseOverable> oldMouseOverables) {
		for(IMouseOverable oldMouseOverable : oldMouseOverables) {
			if(mouseOverables.contains(oldMouseOverable)) {
				mouseOverables.remove(oldMouseOverable);
			}
		}
	}
	
	
	/**
	 * Registers list of elements to the click event element list.
	 */
	public static void registerClickable(ArrayList<IClickable> newClickables) {
		clickables.addAll(newClickables);
	}
	
	
	/**
	 * Unregisters list of elements to the click event element list.
	 */
	public static void unregisterClickable(ArrayList<IClickable> oldClickables) {
		for(IClickable oldClickable : oldClickables) {
			if(clickables.contains(oldClickable)) {
				clickables.remove(oldClickable);
			}
		}
	}
	
	
	/**
	 * Register element to the list of elements in the update cycle.
	 */
	public static void registerUpdatable(IUpdatable updatable) {
		updatables.add(updatable);
	}
	
	
	/**
	 * Unregisters element from the list of elements in the update cycle.
	 */
	public static void unregisterUpdatable(IUpdatable updatable) {
		updatables.remove(updatable);
	}
	
	
	/**
	 * Register element to the list of elements that can be unselected.
	 */
	public static void registerUnselectable(IUnselectable unselectable) {
		unselectables.add(unselectable);
	}
	
	
	/**
	 * Unregisters element from the list of elements that can be unselected.
	 */
	public static void unregisterUnselectable(IUnselectable unselectable) {
		unselectables.remove(unselectable);
	}
	
	
	/**
	 * Process all mouseOver/mouseOut events for all registered elements.
	 */
	public static void processMouseRollOver() {
		Vector2f cursor = inputManager.getCursorPosition();
		int cursorX = (int)cursor.x;
		int cursorY = (int)cursor.y;
		
		for(IMouseOverable element : mouseOverables) {
			int elementStartX = (int)calculateX(element.getOffset(), element.getPositionX());
			int elementEndX = elementStartX + (int)(element.getWidth() * getPixelScale());
			int elementStartY = (int)calculateY(element.getOffset(), element.getPositionY());
			int elementEndY = elementStartY - (int)(element.getHeight() * getPixelScale());
			
			if(elementStartX <= cursorX && cursorX <= elementEndX &&
			   elementStartY >= cursorY && cursorY >= elementEndY) {
				if(!currentMouseOverables.contains(element)) {
					currentMouseOverables.add(element);
					element.onMouseOver();
				}
				
			} else {
				if(currentMouseOverables.contains(element)) {
					currentMouseOverables.remove(element);
					element.onMouseOut();
				}
			}
		}
	}
	
	
	/**
	 * Process all click events for all registered elements.
	 */
	public static void processMouseClick(boolean keyPressed) {
		Vector2f cursor = inputManager.getCursorPosition();
		int cursorX = (int)cursor.x;
		int cursorY = (int)cursor.y;
		
		ArrayList<IClickable> currentClickables = new ArrayList<IClickable>();
		currentClickables.addAll(clickables);
		
		for(IClickable element : currentClickables) {
			int elementStartX = (int)calculateX(element.getOffset(), element.getPositionX());
			int elementEndX = elementStartX + (int)(element.getWidth() * getPixelScale());
			int elementStartY = (int)calculateY(element.getOffset(), element.getPositionY());
			int elementEndY = elementStartY - (int)(element.getHeight() * getPixelScale());
			
			if(elementStartX <= cursorX && cursorX <= elementEndX &&
			   elementStartY >= cursorY && cursorY >= elementEndY) {
				int mouseOffsetX = (int)((float)(cursorX - elementStartX) / getPixelScale());
				int mouseOffsetY = (int)((float)(elementStartY - cursorY) / getPixelScale());
				if(!keyPressed) {
					element.onMouseUp(mouseOffsetX, mouseOffsetY);				
				} else {
					element.onMouseDown(mouseOffsetX, mouseOffsetY);
				}
			}
		}
		
		ArrayList<IUnselectable> currentUnselectables = new ArrayList<IUnselectable>();
		currentUnselectables.addAll(unselectables);
		
		for(IUnselectable element : currentUnselectables) {
			int elementStartX = (int)calculateX(element.getOffset(), element.getPositionX());
			int elementEndX = elementStartX + (int)(element.getWidth() * getPixelScale());
			int elementStartY = (int)calculateY(element.getOffset(), element.getPositionY());
			int elementEndY = elementStartY - (int)(element.getHeight() * getPixelScale());
			
			if(elementStartX > cursorX || cursorX > elementEndX ||
			   elementStartY < cursorY || cursorY < elementEndY) {
				if(!keyPressed) {
					element.onUnselect();				
				}
			}
		}
	}
	
	
	/**
	 * Process update cycle.
	 */
	public static void update(float delta) {
		
		ArrayList<IUpdatable> currentUpdatables = new ArrayList<IUpdatable>();
		currentUpdatables.addAll(updatables);
		
		for(IUpdatable element : currentUpdatables) {
			element.update(delta);
		}
		
		Scenery.update(delta);
		panel.update(delta);
	}
	
	
	/**
	 * Display all static elements of the in-game screen.
	 */
	public static void loadInGameScreen() {
		if(!inGameOn) {
			Scenery.show();
			inGameOn = true;
		}
	}
	
	
	/**
	 * Updates the window of an action location if it is currently open.
	 */
	public static void updateActionLocationState() {
		if(state.getState() == GuiState.State.PALACE_OF_THE_CALIPH) {
			palaceOfTheCaliph.updatePalaceOfTheCaliph();
		} else if(state.getState() == GuiState.State.BANK) {
			bank.updateBank();
		} else if(state.getState() == GuiState.State.CONSTRUCTION_AUTHORITY) {
			constructionAuthority.updateConstructionAuthority();
		} else if(state.getState() == GuiState.State.TOWER_ELEMENT_MARKET) {
			towerElementMarket.updateTowerElementMarket();
		} else if(state.getState() == GuiState.State.HOUSE_OF_SPIES) {
			houseOfSpies.updateHouseOfSpies();
		} else if(state.getState() == GuiState.State.CHEATS) {
			cheatScreen.updateCheatScreen();
		}
	}
	

	/**
	 * Display end of game window.
	 */
	public static void endOfGame() {
		panel.displayEndOfGameWindow();
	}
	
	
	/**
	 * Quit jME3 game.
	 */
	public static void quit() {
		game.stop();
	}


	public static ConnectionScreen getConnectionScreen() {
		return connectionScreen;
	}


	public static GameSetupScreen getGameSetupScreen() {
		return gameSetupScreen;
	}


	public static Panel getPanel() {
		return panel;
	}

	
	public static BankInterface getBank() {
		return bank;
	}
	
	
	public static PalaceOfTheCaliphInterface getPalaceOfTheCaliph() {
		return palaceOfTheCaliph;
	}
	
	
	public static TowerElementMarketInterface getTowerElementMarket() {
		return towerElementMarket;
	}
	
	
	public static HouseOfSpiesInterface getHouseOfSpies() {
		return houseOfSpies;
	}
	
	
	public static ConstructionAuthorityInterface getConstructionAuthority() {
		return constructionAuthority;
	}
	
	
	public static CheatScreen getCheatScreen() {
		return cheatScreen;
	}


	public static int getWindowWidth() {
		return windowWidth;
	}


	public static int getWindowHeight() {
		return windowHeight;
	}


	public static int getPanelWidth() {
		return panelWidth;
	}

	
	public static Node getRootNode() {
		return rootNode;
	}


	public static ArrayList<IMouseOverable> getMouseOverables() {
		return mouseOverables;
	}


	public static ArrayList<IClickable> getClickables() {
		return clickables;
	}


	public static InputManager getInputManager() {
		return inputManager;
	}
	
}
